package prefuse.util;

/**
 * Library routines dealing with Java Class types.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class TypeLib {

    private TypeLib() {
        // prevent instantiation
    }

    // ------------------------------------------------------------------------

    /**
     * Check if an object is an instance of a given class, or, if the class
     * is a primitive type, if the Object is an instance of the wrapper class
     * for that primitive (e.g., as Integer is a wrapper for int).
     * @param type the Class type
     * @param instance the Object instance
     * @return true if the object is an instance of the given class, or if
     * of the appropriate primitive wrapper type.
     */
    public static boolean typeCheck(Class type, Object instance) {
        return type.isAssignableFrom(instance.getClass()) || isWrapperInstance(type, instance);
    }

    /**
     * Get the nearest shared ancestor class of two objects. Note: this
     * currently does not compute the actual least common ancestor, but
     * only looks up one level in the inheritance tree and quits if
     * it does not find a match.
     * @param o1 the first object
     * @param o2 the second object
     * @return the nearest class instance of which both objects
     * are instances
     */
    public static Class getSharedType(Object o1, Object o2) {
        return getSharedType(o1.getClass(), o2.getClass());
    }

    /**
     * Get the nearest shared ancestor class of two classes. Note: this
     * currently does not compute the actual least common ancestor, but
     * only looks up one level in the inheritance tree and quits if
     * it does not find a match.
     * @param type1 the first type
     * @param type2 the second type
     * @return the nearest class instance which is equal to or a
     * superclass of the two class instances
     */
    public static Class getSharedType(Class type1, Class type2) {
        if (type1 == type2) {
            return type1;
        } else if (type1.isAssignableFrom(type2)) {
            return type1;
        } else if (type2.isAssignableFrom(type1)) {
            return type2;
        } else {
            return null;
        }
    }

    /**
     * Indicates if an object is an instance of a wrapper class for a given
     * primitive type.
     * @param type the primitive Class type
     * @param instance the object to test as wrapper (e.g., as Integer is a
     * wrapper type for int)
     * @return true if the object is a wrapper instance of the given
     * primitive type
     */
    public static boolean isWrapperInstance(Class type, Object instance) {
        if (!type.isPrimitive())
            throw new IllegalArgumentException("Input type must be a primitive");

        if (int.class == type && instance instanceof Integer) {
            return true;
        } else if (long.class == type && instance instanceof Long) {
            return true;
        } else if (float.class == type && instance instanceof Float) {
            return true;
        } else if (double.class == type && instance instanceof Double) {
            return true;
        } else if (boolean.class == type && instance instanceof Boolean) {
            return true;
        } else if (short.class == type && instance instanceof Short) {
            return true;
        } else if (byte.class == type && instance instanceof Byte) {
            return true;
        } else if (char.class == type && instance instanceof Character) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Given a numeric (byte, short, int, long, float, or double) class type or
     * associated wrapper class type, return the primitive class type
     * @param type the type to look up, must be a numerical type, but can be
     * either primitive or a wrapper.
     * @return the primitive class type
     */
    public static Class getPrimitiveType(Class type) {
        if (Integer.class.equals(type) || type == int.class) {
            return int.class;
        } else if (Long.class.equals(type) || type == long.class) {
            return long.class;
        } else if (Float.class.equals(type) || type == float.class) {
            return float.class;
        } else if (Double.class.equals(type) || type == double.class) {
            return double.class;
        } else if (Byte.class.equals(type) || type == byte.class) {
            return byte.class;
        } else if (Short.class.equals(type) || type == short.class) {
            return short.class;
        } else {
            throw new IllegalArgumentException("Input class must be a numeric type");
        }
    }

    /**
     * Get the wrapper class type for a primitive class type.
     * @param type a class type
     * @return the wrapper class for the input type if it is a
     * primitive class type, otherwise returns the input type
     */
    public static Class getWrapperType(Class type) {
        if (!type.isPrimitive()) {
            return type;
        } else if (int.class == type) {
            return Integer.class;
        } else if (long.class == type) {
            return Long.class;
        } else if (float.class == type) {
            return Float.class;
        } else if (double.class == type) {
            return Double.class;
        } else if (boolean.class == type) {
            return Boolean.class;
        } else if (short.class == type) {
            return Short.class;
        } else if (char.class == type) {
            return Character.class;
        } else if (byte.class == type) {
            return Byte.class;
        } else if (short.class == type) {
            return Short.class;
        } else {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Indicates if a given class type is a primitive integer type
     * (one of byte, short, int, or long).
     * @param type the type to check
     * @return true if it is a primitive numeric type, false otherwise
     */
    public static boolean isIntegerType(Class type) {
        return (type == byte.class || type == short.class || type == int.class || type == long.class);
    }

    /**
     * Indicates if a given class type is a primitive numeric one type
     * (one of byte, short, int, long, float, or double).
     * @param type the type to check
     * @return true if it is a primitive numeric type, false otherwise
     */
    public static boolean isNumericType(Class type) {
        return (type == byte.class || type == short.class || type == int.class || type == long.class || type == double.class || type == float.class);
    }

    /**
     * Get a compatible numeric type for two primitive numeric
     * class types. Any of (byte, short, int) will resolve to int.
     * @param c1 a numeric primitive class type (int, long, float, or double)
     * @param c2 a numeric primitive class type (int, long, float, or double)
     * @return the compatible numeric type for binary operations involving
     * both types.
     */
    public static Class getNumericType(Class c1, Class c2) {
        if (!isNumericType(c1) || !isNumericType(c2)) {
            throw new IllegalArgumentException("Input types must be primitive number types");
        }
        if (c1 == double.class || c2 == double.class) {
            return double.class;
        } else if (c1 == float.class || c2 == float.class) {
            return float.class;
        } else if (c1 == long.class || c2 == long.class) {
            return long.class;
        } else {
            return int.class;
        }
    }

} // end of class TypeLib
